package lumis.portal.page;

import lumis.portal.PortalException;
import lumis.portal.UnexpectedException;
import lumis.portal.authentication.SessionConfig;
import lumis.portal.cache.ICloneableCacheEntry;
import lumis.portal.channel.ChannelConfig;
import lumis.portal.manager.ManagerFactory;
import lumis.portal.manager.ManagerFactoryInternal;
import lumis.portal.serialization.operation.ICloneableConfig;
import lumis.portal.stability.StableMinor;
import lumis.portal.structure.GenericStructureElementPropertiesAccessor;
import lumis.portal.structure.ILayoutPropertiesAccessor;
import lumis.portal.structure.LayoutPropertiesHelper;
import lumis.portal.structure.StructureElement;
import lumis.portal.transaction.PortalTransactionFactory;
import lumis.util.XmlUtil;

import java.io.OutputStream;
import java.util.regex.Pattern;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.PostLoad;
import javax.persistence.PostPersist;
import javax.persistence.PostRemove;
import javax.persistence.PostUpdate;
import javax.persistence.Table;
import javax.persistence.Transient;

import org.hibernate.annotations.Cache;
import org.hibernate.annotations.CacheConcurrencyStrategy;
import org.hibernate.annotations.NamedQueries;
import org.hibernate.annotations.NamedQuery;
import org.w3c.dom.Node;

/**
 * Represents a Page object
 * 
 * @version $Revision: 13093 $ $Date: 2011-05-28 18:40:18 -0300 (Sat, 28 May 2011) $
 * @since 4.0.6
 */
@StableMinor(version = "6.0", sinceVersion = "5.0")
@Entity
@Table(name="lum_Page")
@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
@NamedQueries( {
		@NamedQuery(name = PageConfig.NAMED_QUERY_FIND_NUMBER_OF_PAGES_USING_SAME_FRIENDLY_ID_FOR_ADD, query = "select count(p.id) from PageConfig p where p.channelId = :channelId and p.friendlyId = :friendlyId"),
		@NamedQuery(name = PageConfig.NAMED_QUERY_FIND_NUMBER_OF_PAGES_USING_SAME_FRIENDLY_ID_FOR_UPDATE, query = "select count(p.id) from PageConfig p where p.channelId = :channelId and p.friendlyId = :friendlyId and not(p.id = :id)") })
public class PageConfig extends StructureElement implements ICloneableCacheEntry, ICloneableConfig<PageConfig>
{
	public static final int PAGE_TYPE_USER = 0;
	public static final int PAGE_TYPE_ADMIN = 1;

	public static final String PAGE_MAIN = "main.jsp";
	
	public static final String PAGE_ID_ADMIN_SERVICES = "LumisServices";
	public static final String PAGE_ID_ADMIN_PAGES = "LumisPages";
	public static final String PAGE_ID_ADMIN_ACCESS = "LumisChannelAccess";
	public static final String PAGE_ID_ADMIN_SUBCHANNELS = "LumisSubChannels";
	public static final String PAGE_ID_ADMIN_PAGE_TEMPLATES = "LumisPageTemplates";
	public static final String PAGE_ID_ADMIN_CHANNEL_TEMPLATES = "LumisChannelTemplates";
	public static final String PAGE_ID_ADMIN_GROUPS = "LumisGroups";
	public static final String PAGE_ID_ADMIN_USERS = "LumisUsers";
	public static final String PAGE_ID_ADMIN_LOCALGROUPS = "LumisLocalGroups";
	public static final String PAGE_ID_ADMIN_LOCALUSERS = "LumisLocalUsers";
	public static final String PAGE_ID_ADMIN_BLANK = "LumisBlankPage";

	public static final int PAGE_CACHE_TYPE_DISABLED = 0;
	public static final int PAGE_CACHE_TYPE_ALL_USERS = 1;
	public static final int PAGE_CACHE_TYPE_ONLY_GUEST = 2;
	public static final int PAGE_CACHE_TYPE_INHERIT = 3;
	
	public static final int PAGE_CENTER_NO = 0;
	public static final int PAGE_CENTER_YES = 1;
	public static final int PAGE_CENTER_INHERIT = 2;

	static final String NAMED_QUERY_FIND_NUMBER_OF_PAGES_USING_SAME_FRIENDLY_ID_FOR_ADD = "PageConfig#findNumberOfPagesUsingFriendlyId#Add";
	static final String NAMED_QUERY_FIND_NUMBER_OF_PAGES_USING_SAME_FRIENDLY_ID_FOR_UPDATE = "PageConfig#findNumberOfPagesUsingFriendlyId#Update";
	
	/**
	 * Page layout type for inherit type.
	 * @since 4.2.0
	 */
	public static final int PAGE_LAYOUT_TYPE_INHERIT = 0;
	
	/**
	 * Page layout type for layout using tables.
	 * @since 4.2.0
	 */
	public static final int PAGE_LAYOUT_TYPE_WITH_TABLES = 1;
	
	/**
	 * Page layout type for layout not using tables.
	 * @since 4.2.0
	 */
	public static final int PAGE_LAYOUT_TYPE_TABLELESS = 2;
	
	/**
	 * Page layout type for layout using layout file.
	 * @since 5.6.0
	 */
	public static final int PAGE_LAYOUT_TYPE_LAYOUT_FILE = 3;
	
	/**
	 * Property name for layout file property.
	 * @since 5.6.0
	 */
	public static final String LAYOUT_FILE_PATH_PROPERTY_NAME = "lumis.portal.page.layoutFile";
	
	/**
	 * Pattern used to detect managed properties for pages.
	 * @since 5.6.0
	 */
	private static final Pattern MANAGED_PROPERTIES_PATTERN = Pattern.compile("lumis\\.portal\\.page\\..*");

	/**
	 * Constant that indicates to not process server side include directives.
	 * @since 4.0.11
	 */
	public static final int PAGE_SERVER_SIDE_INCLUDE_NO = 0;

	/**
	 * Constant that indicates to process server side include directives.
	 * @since 4.0.11
	 */
	public static final int PAGE_SERVER_SIDE_INCLUDE_YES = 1;

	
	public static final int PAGE_PROTOCOL_INHERIT = 0;
	public static final int PAGE_PROTOCOL_HTTP = 1;
	public static final int PAGE_PROTOCOL_HTTPS = 2;
	public static final int PAGE_PROTOCOL_ALL = 3;
	public static final String PAGE_PROTOCOL_HTTP_NAME = "http";
	public static final String PAGE_PROTOCOL_HTTPS_NAME = "https";
	
	/**
	 * @deprecated Since 5.0.0 replaced by {@link #PAGE_WEB_RESOURCE_DATA_PROVIDER_TYPE_INHERIT()}.
	 * @since 4.2.0
	 */
	@Deprecated
	public static final int PAGE_RUNTIME_DATA_PROVIDER_TYPE_INHERIT = 0;
	/**
	 * @deprecated Since 5.0.0 replaced by {@link #PAGE_WEB_RESOURCE_DATA_PROVIDER_TYPE_DEFAULT()}.
	 * @since 4.2.0
	 */
	@Deprecated
	public static final int PAGE_RUNTIME_DATA_PROVIDER_TYPE_DEFAULT = 1;
	/**
	 * @deprecated Since 5.0.0 replaced by {@link #PAGE_WEB_RESOURCE_DATA_PROVIDER_TYPE_CONTENT()}.
	 * @since 4.2.0
	 */
	@Deprecated
	public static final int PAGE_RUNTIME_DATA_PROVIDER_TYPE_CONTENT = 2;
	/**
	 * @deprecated Since 5.0.0 replaced by {@link #PAGE_WEB_RESOURCE_DATA_PROVIDER_TYPE_CUSTOM()}.
	 * @since 4.2.0
	 */	
	@Deprecated
	public static final int PAGE_RUNTIME_DATA_PROVIDER_TYPE_CUSTOM = 3;
	
	public static final int PAGE_WEB_RESOURCE_DATA_PROVIDER_TYPE_INHERIT = 0;
	public static final int PAGE_WEB_RESOURCE_DATA_PROVIDER_TYPE_DEFAULT = 1;
	public static final int PAGE_WEB_RESOURCE_DATA_PROVIDER_TYPE_CONTENT = 2;
	public static final int PAGE_WEB_RESOURCE_DATA_PROVIDER_TYPE_CUSTOM = 3;

	@Id
	@Column(name="pageId", nullable=false)
	private String id = null;
	private String name = null;
	private String description = null;
	private String channelId = null;
	private int position = 0;
	private String layout = null;
	private boolean hidden = false;
	private String parentTemplateId = null;
	private String keywords = null;
	@Column(name="pageTitle")
	private String title = null;
	@Column(name="cachePage")
	private int cacheType = PAGE_CACHE_TYPE_INHERIT;
	@Column(name="inheritStylesheet")
	private boolean inheritsStylesheets = true;
	private int type = PageConfig.PAGE_TYPE_USER;
	private boolean isTemplate = false;
	private String accessControlListId = null;
	@Column(name="inheritMetaTags")
	private boolean inheritsMetaTags = true;
	private int centerPage = PAGE_CENTER_INHERIT;
	private int layoutType = PAGE_LAYOUT_TYPE_INHERIT;
	@Column(name="overrideSsi")
	private boolean overrideServerSideInclude = false;
	@Column(name="ssiType")
	private int serverSideIncludeType = PAGE_SERVER_SIDE_INCLUDE_NO;
	private int protocol = PAGE_PROTOCOL_INHERIT;
	
	//these fields were not renamed to keep compatibility with 4.2 LEC files
	@Column(name="pageWebResDataProvType")
	private int runtimeDataProviderType = PAGE_WEB_RESOURCE_DATA_PROVIDER_TYPE_INHERIT;
	@Column(name="pageWebResDataProv")
	private String runtimeDataProviderClassName = null;

	/**
	 * This element's property bag identifier.
	 * @since 5.0.0
	 */
	@Column(updatable=false)
	private String propertyBagId;
	
	private String friendlyId = null;
	private Boolean automaticFriendlyId = true;
	
	/**
	 * The accessor for page layout attributes.
	 *
	 * @version $Revision: 13093 $ $Date: 2011-05-28 18:40:18 -0300 (Sat, 28 May 2011) $
	 * @since 5.6.0
	 */
	private static class PageLayoutPropertiesAccessor extends GenericStructureElementPropertiesAccessor implements ILayoutPropertiesAccessor
	{
		private final PageConfig pageConfig;
		
		public PageLayoutPropertiesAccessor(PageConfig pageConfig)
		{
			super(pageConfig);
			this.pageConfig = pageConfig;
		}
		
		public int getLayoutType()
		{
			return this.pageConfig.layoutType;
		}

		public void setLayoutType(int layoutType)
		{
			this.pageConfig.layoutType = layoutType;
		}
	}
	
	/**
	 * Enum of audit types used by page.
	 *
	 * @version $Revision: 13093 $ $Date: 2011-05-28 18:40:18 -0300 (Sat, 28 May 2011) $
	 * @since 6.0.0
	 */
	private static enum AuditType
	{
		ADD("lumis.portal.page.PageConfig#add", "Added page"),
		UPDATE("lumis.portal.page.PageConfig#update", "Updated page"),
		DELETE("lumis.portal.page.PageConfig#delete", "Deleted page");
		
		String key;
		String title;
		
		AuditType(String key, String title)
		{
			this.key = key;
			this.title = title;
		}
	}
	
	/**
	 * @since 5.6.0
	 */
	@Transient
	private LayoutPropertiesHelper layoutPropertiesHelper = new LayoutPropertiesHelper(new PageLayoutPropertiesAccessor(PageConfig.this));
	
	@Override
	protected Pattern getManagedPropertiesPattern()
	{
		return MANAGED_PROPERTIES_PATTERN;
	}
	
	@PostLoad
	@Override
	protected void loadPropertyBagValues() throws PortalException
	{
		super.loadPropertyBagValues();
	}
	
	@Override
	protected void applyPropertyBagChanges() throws PortalException
	{
		super.applyPropertyBagChanges();
	}
	
	/**
	 * Returns the path for the layout file for use by this page.
	 * <p>
	 * The layout file may be inherited to child elements.
	 * <p>
	 * If {@link #getLayoutType()} is not {@link #PAGE_LAYOUT_TYPE_LAYOUT_FILE} (or inherited it),
	 * this method will return <code>null</code> since no layout file is to be used.
	 * 
	 * @return layoutFile the path for the layout file, relative to the <code>&lt;lumisdata&gt;/def</code> directory,
	 * possibly inherited from parent channel; or <code>null</code> if no layout file is to be used.
	 * 
	 * @see #getLayoutPagesType()
	 * @see #setLayoutFile(String)
	 * 
	 * @since 5.6.0
	 */
	public String getLayoutFilePath()
	{
		try
		{
			int layoutType = ManagerFactory.getPageManager().getLayoutType(SessionConfig.getCurrentSessionConfig(), this, PortalTransactionFactory.getCurrentTransaction());
			if (layoutType == PAGE_LAYOUT_TYPE_LAYOUT_FILE)
				return getPropertyValue(LAYOUT_FILE_PATH_PROPERTY_NAME);
			else
				return null;
		}
		catch (PortalException e)
		{
			throw new RuntimeException(e);
		}
	}
	
	/**
	 * Sets the path for the layout file.
	 * <p>
	 * The layout file may be inherited to child elements.
	 * <p>
	 * When the layout file is set, {@link #getLayoutType()} is automatically changed to
	 * {@link PageConfig#PAGE_LAYOUT_TYPE_LAYOUT_FILE}. 
	 * 
	 * @param layoutFile the path relative to the <code>&lt;lumisdata&gt;/def</code> directory.
	 * 
	 * @see #setLayoutType(int)
	 * 
	 * @since 5.6.0
	 */
	public void setLayoutFile(String layoutFile)
	{
		layoutPropertiesHelper.setLayoutFilePath(layoutFile);
	}
	
	public PageConfig()
	{
		super();
	}

	/**
	 * @return Returns the pageId.
	 */
	public String getId()
	{
		return id;
	}

	/**
	 * @param Id
	 *            The pageId to set.
	 */
	public void setId(String id)
	{
		this.id = id;
	}

	/**
	 * @return Returns the name.
	 */
	public String getName()
	{
		return name;
	}

	/**
	 * @param name
	 *            The name to set.
	 */
	public void setName(String name)
	{
		this.name = name;
	}

	/**
	 * @return Returns the description.
	 */
	public String getDescription()
	{
		return description;
	}

	/**
	 * @param description
	 *            The description to set.
	 */
	public void setDescription(String description)
	{
		this.description = description;
	}

	/**
	 * @return Returns the layout.
	 */
	public String getLayout()
	{
		return layout;
	}

	/**
	 * @param layout
	 *            The layout to set.
	 */
	public void setLayout(String layout)
	{
		this.layout = layout;
	}

	/**
	 * @return Returns the channelId.
	 */
	public String getChannelId()
	{
		return channelId;
	}

	/**
	 * @param channelId
	 *            The channelId to set.
	 */
	public void setChannelId(String channelId)
	{
		this.channelId = channelId;
	}

	/**
	 * @return Returns the cacheType.
	 */
	public int getCacheType()
	{
		return cacheType;
	}

	/**
	 * @param cacheType
	 *            The type of cache to set.
	 */
	public void setCacheType(int cacheType)
	{
		this.cacheType = cacheType;
	}

	/**
	 * @return Returns the hidden.
	 */
	public boolean isHidden()
	{
		return hidden;
	}

	/**
	 * @param hidden
	 *            The hidden to set.
	 */
	public void setHidden(boolean hidden)
	{
		this.hidden = hidden;
	}

	/**
	 * @return Returns the inheritsStylesheets.
	 */
	
	public boolean getInheritsStylesheets()
	{
		return inheritsStylesheets;
	}
	
	/**
	 * @return Returns the inheritsMetaTags.
	 */
	public boolean getInheritsMetaTags()
	{
		return inheritsMetaTags;
	}

	/**
	 * @param inheritsStylesheets
	 *            The inheritsStylesheets to set.
	 */
	public void setInheritsStylesheets(boolean inheritsStylesheets)
	{
		this.inheritsStylesheets = inheritsStylesheets;
	}
	
	/**
	 * @param inheritsMetaTags
	 * 				The inheritsMetaTags to set.
	 */
	public void setInheritsMetaTags(boolean inheritsMetaTags)
	{
		this.inheritsMetaTags = inheritsMetaTags;
	}
	
	/**
	 * @return Returns the protocol.
	 */
	public int getProtocol()
	{
		return protocol;
	}

	/**
	 * @param protocol
	 *            The protocol to set.
	 */
	public void setProtocol(int protocol)
	{
		this.protocol = protocol;
	}
	
	/**
	 * @return Returns the keywords.
	 */
	public String getKeywords()
	{
		return keywords;
	}

	/**
	 * @param keywords
	 *            The keywords to set.
	 */
	public void setKeywords(String keywords)
	{
		this.keywords = keywords;
	}

	/**
	 * @return Returns the title.
	 */
	public String getTitle()
	{
		return title;
	}

	/**
	 * @param title
	 *            The title to set.
	 */
	public void setTitle(String title)
	{
		this.title = title;
	}

	/**
	 * @return Returns the parentTemplateId.
	 */
	public String getParentTemplateId()
	{
		return parentTemplateId;
	}

	/**
	 * @param parentTemplateId
	 *            The parentTemplateId to set.
	 */
	public void setParentTemplateId(String parentTemplateId)
	{
		this.parentTemplateId = parentTemplateId;
	}

	/**
	 * @return Returns the position.
	 */
	public int getPosition()
	{
		return position;
	}

	/**
	 * @param position
	 *            The position to set.
	 */
	public void setPosition(int position)
	{
		this.position = position;
	}

	/**
	 * @return Returns the position.
	 */
	public int getType()
	{
		return type;
	}

	/**
	 * @param position
	 *            The position to set.
	 */
	public void setType(int type)
	{
		this.type = type;
	}

	/**
	 * @return Returns the isTemplate.
	 */
	public boolean isTemplate()
	{
		return isTemplate;
	}

	/**
	 * @param isTemplate
	 *            The isTemplate to set.
	 */
	public void setTemplate(boolean isTemplate)
	{
		this.isTemplate = isTemplate;
	}

	public String getAccessControlListId()
	{
		return accessControlListId;
	}

	public void setAccessControlListId(String accessControlListId)
	{
		this.accessControlListId = accessControlListId;
	}
	
	@Override
	public String getPropertyBagId()
	{
		return propertyBagId;
	}

	/**
	 * Sets the property bag identifier.
	 * <p>
	 * This method should be used only for non-persisted page, 
	 * because this property value may not be changed.
	 * @param propertyBagId The propertyBagId to set.
	 * @since 5.0.0
	 */
	protected void setPropertyBagId(String propertyBagId)
	{
		if (this.propertyBagId != null && !this.propertyBagId.equals(propertyBagId))
			throw new IllegalStateException("propertyBagId already set");
		
		this.propertyBagId = propertyBagId;
	}

	public int getCenterPage()
	{
		return centerPage;
	}

	public void setCenterPage(int centerPage)
	{
		this.centerPage = centerPage;
	}

	public int getLayoutType()
	{
		return layoutType;
	}

	/**
	 * Sets the layout type.
	 * <p>
	 * To set {@link PageConfig#PAGE_LAYOUT_TYPE_LAYOUT_FILE} value use the method {@link #setLayoutFile(String)},
	 * and the layout type is changed automatically. {@link PageConfig#PAGE_LAYOUT_TYPE_LAYOUT_FILE} cannot
	 * be set directly with this method.
	 * <p>
	 * If set to a value different of {@link PageConfig#PAGE_LAYOUT_TYPE_LAYOUT_FILE}, the {@link #getLayoutFilePath()} will
	 * be changed to <code>null</code>.
	 * 
	 * @see #setLayoutFile(String).
	 * 
	 * @param layoutType the page layout type.
	 * @throws IllegalArgumentException If called with the argument {@link PageConfig#PAGE_LAYOUT_TYPE_LAYOUT_FILE}.
	 * @since 4.2.0
	 */
	public void setLayoutType(int layoutType)
	{
		layoutPropertiesHelper.setLayoutType(layoutType);
	}
	
	/**
	 * Sets the layout type for pages.
	 * 
	 * @param layoutPagesType the layout type to set for pages.
	 *            
	 * @since 5.6.0
	 */
	void setLayoutTypeInternal(int layoutType)
	{
		this.layoutType = layoutType;
	}

	public void serialize(PageConfig pageConfig, OutputStream outputStream) throws PortalException
	{
		try
		{
			StringBuilder stream = new StringBuilder();
			
			stream.append("<page id=\""+getId()+"\">");
			stream.append("<name>" + XmlUtil.encodeXml(getName()) + "</name>");
			stream.append("<title>" + XmlUtil.encodeXml(getTitle()) + "</title>");
			stream.append("<type>"+getType()+"</type>");
			stream.append("<aclId>"+getAccessControlListId()+"</aclId>");
			stream.append("<centerPage>" + getCenterPage() + "</centerPage>");
			stream.append("<layoutType>" + getLayoutType() + "</layoutType>");
			stream.append("<cacheType>"+getCacheType()+"</cacheType>");
			stream.append("<channelId>"+(getChannelId() == null ? "" : getChannelId())+"</channelId>");
			stream.append("<description>" + XmlUtil.encodeXml(getDescription()) + "</description>");
			stream.append("<hidden>"+isHidden()+"</hidden>");
			stream.append("<inheritStylesheets>"+getInheritsStylesheets()+"</inheritStylesheets>");
			stream.append("<inheritMetaTags>"+getInheritsMetaTags()+"</inheritMetaTags>");
			stream.append("<propertyBagId>" + getPropertyBagId() + "</propertyBagId>");
			stream.append("<isTemplate>"+isTemplate()+"</isTemplate>");
			stream.append("<keywords>" + XmlUtil.encodeXml(getKeywords()) + "</keywords>");
			stream.append("<layout>" + XmlUtil.encodeXml(getLayout()) + "</layout>");
			stream.append("<parentTemplateId>"+(getParentTemplateId() == null ? "" : getParentTemplateId())+"</parentTemplateId>");
			stream.append("<position>"+getPosition()+"</position>");
			stream.append("<overrideServerSideInclude>"+isOverrideServerSideInclude()+"</overrideServerSideInclude>");
			stream.append("<serverSideIncludeType>"+getServerSideIncludeType()+"</serverSideIncludeType>");
			stream.append("<protocol>" + getProtocol() + "</protocol>");
			stream.append("<pageWebResourceDataProviderType>" + getPageWebResourceDataProviderType() + "</pageWebResourceDataProviderType>");
			stream.append("<pageWebResourceDataProviderClassName>" + (getPageWebResourceDataProviderClassName() == null ? "" : getPageWebResourceDataProviderClassName()) + "</pageWebResourceDataProviderClassName>");
			stream.append("<automaticFriendlyId>" + Boolean.toString(isAutomaticFriendlyId())
					+ "</automaticFriendlyId>");
			stream.append("<friendlyId>" + getFriendlyId() + "</friendlyId>");

			// serialize propertyBag
			stream.append(serializePropertyBag());
						
			stream.append("</page>");
	
			outputStream.write(stream.toString().getBytes("UTF-8"));
		}
		catch(Exception e)
		{
			throw new UnexpectedException(e);
		}
	}
	
	public void deserialize(Node pageNode) throws PortalException
	{
		try
		{
			setId(XmlUtil.readAttributeString("id", pageNode));
			setName(XmlUtil.readNodeString("name", pageNode));
			setTitle(XmlUtil.readNodeString("title", pageNode));
			setType(XmlUtil.readNodeInt("type", pageNode, getType()));
			setAccessControlListId(XmlUtil.readNodeString("aclId", pageNode, "").equals("") ? null : XmlUtil.readNodeString("aclId", pageNode));
			setCenterPage(XmlUtil.readNodeInt("centerPage", pageNode, getCenterPage()));
			setLayoutTypeInternal(XmlUtil.readNodeInt("layoutType", pageNode, getLayoutType()));
			setCacheType(XmlUtil.readNodeInt("cacheType", pageNode, getCacheType()));
			setChannelId(XmlUtil.readNodeString("channelId", pageNode));
			setDescription(XmlUtil.readNodeString("description", pageNode));
			setHidden(XmlUtil.readNodeBoolean("hidden", pageNode, isHidden()));
			setInheritsStylesheets(XmlUtil.readNodeBoolean("inheritStylesheets", pageNode, getInheritsStylesheets()));
			setInheritsMetaTags(XmlUtil.readNodeBoolean("inheritMetaTags", pageNode, getInheritsMetaTags()));
			setPropertyBagId(XmlUtil.readNodeString("propertyBagId", pageNode));
			setTemplate(XmlUtil.readNodeBoolean("isTemplate", pageNode, isTemplate()));
			setKeywords(XmlUtil.readNodeString("keywords", pageNode));
			setLayout(XmlUtil.readNodeString("layout", pageNode));
			setParentTemplateId(XmlUtil.readNodeString("parentTemplateId", pageNode));
			setPosition(XmlUtil.readNodeInt("position", pageNode, getPosition()));
			setOverrideServerSideInclude(XmlUtil.readNodeBoolean("overrideServerSideInclude", pageNode, isOverrideServerSideInclude()));
			setServerSideIncludeType(XmlUtil.readNodeInt("serverSideIncludeType", pageNode, getServerSideIncludeType()));
			setProtocol(XmlUtil.readNodeInt("protocol", pageNode, getProtocol()));
			setPageWebResourceDataProviderType(XmlUtil.readNodeInt("pageWebResourceDataProviderType", pageNode, PageConfig.PAGE_RUNTIME_DATA_PROVIDER_TYPE_INHERIT));
			setPageWebResourceDataProviderClassName(XmlUtil.readNodeString("pageWebResourceDataProviderClassName", pageNode));
			setAutomaticFriendlyId(XmlUtil.readNodeBoolean("automaticFriendlyId", pageNode,
					isAutomaticFriendlyId()));
			setFriendlyId(XmlUtil.readNodeString("friendlyId", pageNode));
		}
		catch(PortalException e)
		{
			throw e;
		}
		catch(Exception e)
		{
			throw new UnexpectedException(e);
		}
	}
	
	@Override
	public PageConfig clone()
	{
		try
		{
			PageConfig clone = (PageConfig)super.clone();
			clone.layoutPropertiesHelper = new LayoutPropertiesHelper(new PageLayoutPropertiesAccessor(clone));
			return clone;
		}
		catch(CloneNotSupportedException e)
		{
			// should never happen
			throw new RuntimeException(e);
		}
	}

	/**
	 * Indicates whether the server side include type should be calculated automatically by the framework.
	 * <p>
	 * By default this value is false.
	 * 
	 * @return false if server-side include type is calculated by the framework.  true otherwise.
	 * @since 4.1.0
	 * @see #getServerSideIncludeType()
	 */
	public boolean isOverrideServerSideInclude()
	{
		return overrideServerSideInclude;
	}

	/**
	 * Sets whether the server side include type should be calculated automatically by the framework.
	 * 
	 * @param overrideServerSideInclude false if server-side include type is calculated by the framework.  true otherwise.
	 * @since 4.1.0
	 * @see #getServerSideIncludeType()
	 */
	public void setOverrideServerSideInclude(boolean overrideServerSideInclude)
	{
		this.overrideServerSideInclude = overrideServerSideInclude;
	}

	/**
	 * Returns whether the server side include directives are processed for this page.
	 *  
	 * @return either {@value #PAGE_SERVER_SIDE_INCLUDE_NO} or {@value #PAGE_SERVER_SIDE_INCLUDE_YES}
	 * @since 4.1.0
	 */
	public int getServerSideIncludeType()
	{
		return serverSideIncludeType;
	}

	/**
	 * Sets whether the server side include directives should be processed for this page.
	 * 
	 * @param serverSideIncludeType either {@value #PAGE_SERVER_SIDE_INCLUDE_NO} or {@value #PAGE_SERVER_SIDE_INCLUDE_YES}
	 * @since 4.1.0
	 */
	public void setServerSideIncludeType(int serverSideIncludeType)
	{
		this.serverSideIncludeType = serverSideIncludeType;
	}

	/**
	 * Returns the runtime data provider type.
	 * 
	 * The possible values are 
	 * {@value #PAGE_RUNTIME_DATA_PROVIDER_TYPE_INHERIT} or 
	 * {@value #PAGE_RUNTIME_DATA_PROVIDER_TYPE_DEFAULT} or 
	 * {@value #PAGE_RUNTIME_DATA_PROVIDER_TYPE_CONTENT} or 
	 * {@value #PAGE_RUNTIME_DATA_PROVIDER_TYPE_CUSTOM}
	 * 
	 * @return the runtime data provider type.
	 * @since 4.2.0
	 * 
	 * @deprecated Since 5.0.0 replaced by {@link #getPageWebResourceDataProviderType()}.
	 */
	@Deprecated
	public int getRuntimeDataProviderType()
	{
		return getPageWebResourceDataProviderType();
	}

	/**
	 * Sets the runtime data provider type.
	 * 
	 * The possible values are 
	 * {@value #PAGE_RUNTIME_DATA_PROVIDER_TYPE_INHERIT} or 
	 * {@value #PAGE_RUNTIME_DATA_PROVIDER_TYPE_DEFAULT} or 
	 * {@value #PAGE_RUNTIME_DATA_PROVIDER_TYPE_CONTENT} or 
	 * {@value #PAGE_RUNTIME_DATA_PROVIDER_TYPE_CUSTOM}
	 * 
	 * @param runtimeDataProviderType the runtime data provider type.
	 * @since 4.2.0
	 * 
	 * @deprecated Since 5.0.0 replaced by {@link #setPageWebResourceDataProviderType(int)}.
	 */
	@Deprecated
	public void setRuntimeDataProviderType(int runtimeDataProviderType)
	{
		setPageWebResourceDataProviderType(runtimeDataProviderType);
	}

	/**
	 * Returns the class name for the page runtime data provider.
	 * 
	 * This value should only be used if the runtime data provider type is {@value PAGE_RUNTIME_DATA_PROVIDER_TYPE_CUSTOM}
	 * 
	 * @return the runtime data provider class name
	 * @since 4.2.0
	 * 
	 * @deprecated Since 5.0.0 replaced by {@link #getPageWebResourceDataProviderClassName()}.
	 */
	@Deprecated
	public String getRuntimeDataProviderClassName()
	{
		return getPageWebResourceDataProviderClassName();
	}

	/**
	 * Sets the runtime data provider class name.
	 * 
	 * @param runtimeDataProviderClassName the class name to be set.
	 * @since 4.2.0
	 * 
	 * @deprecated Since 5.0.0 replaced by {@link #setPageWebResourceDataProviderClassName(String)}.
	 */
	@Deprecated
	public void setRuntimeDataProviderClassName(String runtimeDataProviderClassName)
	{
		setPageWebResourceDataProviderClassName(runtimeDataProviderClassName);
	}
	
	/**
	 * Returns the class name for the page web resource data provider.
	 * <p>
	 * This value should only be used if the page web resource data provider type is
	 * {@link #PAGE_WEB_RESOURCE_DATA_PROVIDER_TYPE_CUSTOM}.
	 * 
	 * @return the page web resource data provider class name.
	 * @since 5.0.0
	 */
	public String getPageWebResourceDataProviderClassName()
	{
		return runtimeDataProviderClassName;
	}

	/**
	 * Sets the page web resource data provider class name.
	 * 
	 * @param dataProviderClassName the class name to be set.
	 * @since 5.0.0
	 */
	public void setPageWebResourceDataProviderClassName(String dataProviderClassName)
	{
		this.runtimeDataProviderClassName = dataProviderClassName;
	}
	
	/**
	 * Returns the page web resource data provider type.
	 * <p>
	 * The possible values are 
	 * {@link PageConfig#PAGE_WEB_RESOURCE_DATA_PROVIDER_TYPE_INHERIT} or 
	 * {@link PageConfig#PAGE_WEB_RESOURCE_DATA_PROVIDER_TYPE_DEFAULT} or 
	 * {@link PageConfig#PAGE_WEB_RESOURCE_DATA_PROVIDER_TYPE_CONTENT} or 
	 * {@link PageConfig#PAGE_WEB_RESOURCE_DATA_PROVIDER_TYPE_CUSTOM}
	 * 
	 * @return the page web resource data provider type.
	 * @since 5.0.0
	 */
	public int getPageWebResourceDataProviderType()
	{
		return runtimeDataProviderType;
	}
	
	/**
	 * Sets the page web resource data provider type.
	 * <p>
	 * The possible values are 
	 * {@link #PAGE_WEB_RESOURCE_DATA_PROVIDER_TYPE_INHERIT} or 
	 * {@link #PAGE_WEB_RESOURCE_DATA_PROVIDER_TYPE_DEFAULT} or 
	 * {@link #PAGE_WEB_RESOURCE_DATA_PROVIDER_TYPE_CONTENT} or 
	 * {@link #PAGE_WEB_RESOURCE_DATA_PROVIDER_TYPE_CUSTOM}
	 * 
	 * @param dataProviderType the page web resource data provider type.
	 * @since 5.0.0
	 */
	public void setPageWebResourceDataProviderType(int dataProviderType)
	{
		this.runtimeDataProviderType = dataProviderType;
	}

	/**
	 * Sets the page's friendly identifier. This value must be unique among the
	 * pages contained in the same channel this page is contained.
	 * 
	 * @param friendlyId
	 *            the page's friendly identifier.
	 * @since 6.0.0
	 */
	public void setFriendlyId(String friendlyId)
	{
		this.friendlyId = friendlyId;
	}

	/**
	 * Generates a new friendly identifier and sets it as the friendly
	 * identifier of this page. The friendly identifier is generated based
	 * on this page's current name.
	 * <p>
	 * This method requires an active transaction.
	 * @see #setFriendlyId(String)
	 * @since 6.0.0
	 */
	public void setAutoGeneratedFriendlyId()
	{
		try
		{
			setFriendlyId(getAutomaticFriendlyId(getName()));
		}
		catch (PortalException e)
		{
			throw new RuntimeException(e);
		}
	}
	
	/**
	 * Returns the page's friendly identifier.
	 * 
	 * @return the page's friendly identifier.
	 * @since 6.0.0
	 */
	public String getFriendlyId()
	{
		return friendlyId;
	}

	/**
	 * Indicates whether the page is going to use automatic generated friendly
	 * identifier or not.
	 * 
	 * @param automaticFriendlyId
	 *            indicates whether the page is going to use automatic generated
	 *            friendly identifier or not.
	 * @since 6.0.0
	 */
	public void setAutomaticFriendlyId(boolean automaticFriendlyId)
	{
		this.automaticFriendlyId = automaticFriendlyId;
	}

	/**
	 * Returns whether the page is going to use automatic generated friendly
	 * identifier or not.
	 * 
	 * @return whether the page is going to use automatic generated friendly
	 *         identifier or not.
	 * @since 6.0.0
	 */
	public boolean isAutomaticFriendlyId()
	{
		return automaticFriendlyId;
	}
	
	/**
	 * Returns true if all properties of this object are equals to the properties of the given object.
	 * @param pageConfig the object to compare the properties with.
	 * @return true if the properties are equal, false otherwise.
	 * @since 5.6.0
	 */
	public boolean isIdentical(PageConfig pageConfig)
	{
		return 
			areEqual(this.accessControlListId, pageConfig.accessControlListId)
			&& this.inheritsMetaTags == pageConfig.inheritsMetaTags
			&& this.inheritsStylesheets == pageConfig.inheritsStylesheets
			&& this.isTemplate == pageConfig.isTemplate
			&& this.cacheType == pageConfig.cacheType
			&& this.overrideServerSideInclude == pageConfig.overrideServerSideInclude
			&& this.centerPage == pageConfig.centerPage
			&& this.layoutType == pageConfig.layoutType
			&& areEqual(this.channelId, pageConfig.channelId)
			&& areEqual(this.description, pageConfig.description)
			&& areEqual(this.id, pageConfig.id)
			&& areEqual(this.keywords, pageConfig.keywords)
			&& areEqual(this.layout, pageConfig.layout)
			&& areEqual(this.name, pageConfig.name)
			&& areEqual(this.parentTemplateId, pageConfig.parentTemplateId)
			&& areEqual(this.runtimeDataProviderClassName, pageConfig.runtimeDataProviderClassName)
			&& areEqual(this.title, pageConfig.title)
			&& this.hidden == pageConfig.hidden
			&& this.position == pageConfig.position
			&& this.protocol == pageConfig.protocol
			&& this.runtimeDataProviderType == pageConfig.runtimeDataProviderType
			&& this.serverSideIncludeType == pageConfig.serverSideIncludeType
			&& this.type == pageConfig.type;
	}

	private boolean areEqual(Object obj1, Object obj2)
	{
		return ((obj1 == obj2) || (obj1 != null && obj1.equals(obj2)));
	}
	
	@PostPersist
	void doPostPersist() throws PortalException
	{
		audit(AuditType.ADD);
	}

	@PostUpdate
	void doPostUpdate() throws PortalException
	{
		audit(AuditType.UPDATE);
	}
	
	@PostRemove
	void doPostRemove() throws PortalException
	{
		audit(AuditType.DELETE);
	}
	
	/**
	 * Call the audit API registering the page operations.
	 * @param auditType			the audit operation type.
	 * @throws PortalException	if an error occur registering the audit operation.
	 * @since 6.0.0
	 */
	private void audit(AuditType auditType) throws PortalException
	{
		ChannelConfig channelConfig = null;
		if (this.channelId != null)
		{
			try
			{
				channelConfig = ManagerFactory.getChannelManager().get(
						SessionConfig.getCurrentSessionConfig(), 
						channelId, 
						PortalTransactionFactory.getCurrentTransaction());
			}
			catch (Exception e)
			{
			}
		}
		
		String title = auditType.title + ": " + this.getName();
		String description = auditType.title + ":\r\n"
								+ "  id: " + this.getId() + "\r\n" 
								+ "  name: " + this.getName() + "\r\n"
								+ "  description: " + this.getDescription() + "\r\n"
								+ "  channel id: " + this.channelId
								+ ((channelConfig != null)?"\r\n  channel name: " + channelConfig.getName():"");
		
		ManagerFactoryInternal.getAuditManager().audit(title, description, auditType.key);
	}

	@Override
	protected boolean mayUseFriendlyId(String friendlyId) throws PortalException
	{
		return ((PageManager) ManagerFactoryInternal.getPageManager()).mayUseFriendlyId(this,
				friendlyId);
	}
}